package com.sandy.demo.auth

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.web.filter.GenericFilterBean

import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@Slf4j
@CompileStatic
class RestTokenValidationFilter extends GenericFilterBean {

    RestAuthenticationProvider restAuthenticationProvider

    AuthenticationSuccessHandler authenticationSuccessHandler

    AuthenticationFailureHandler authenticationFailureHandler

    String validationEndpointUrl

    Boolean active

    @Override
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        def req = request as HttpServletRequest
        def res = response as HttpServletResponse

        try {
            def accessToken = findToken(req)
            if (accessToken) {
                log.debug "Token found: ${accessToken.accessToken}"

                log.debug "Trying to authenticate the token"
                accessToken = restAuthenticationProvider.authenticate(accessToken) as AccessToken

                if (accessToken.authenticated) {
                    log.debug "Token authenticated. Storing the authentication result in the security context"
                    log.debug "Authentication result: ${accessToken}"
                    SecurityContextHolder.context.authentication = accessToken

                    processFilterChain(request, response, chain, accessToken)
                }

            } else {
                log.debug "Token not found"
                processFilterChain(request, response, chain, accessToken)
            }
        } catch (AuthenticationException ae) {
            log.debug "Authentication failed: ${ae.message}"
            authenticationFailureHandler.onAuthenticationFailure(req, res, ae)
        }

    }

    private void processFilterChain(ServletRequest request, ServletResponse response, FilterChain chain, AccessToken authResult) {
        def req = request as HttpServletRequest
        def res = response as HttpServletResponse

        def actualUri = req.requestURI - req.contextPath

        if (!active) {
            log.debug "Token validation is disabled. Continuing the filter chain"
            chain.doFilter(request, response)
            return
        }

        if (authResult?.accessToken) {
            if (actualUri == validationEndpointUrl) {
                log.debug "Validation endpoint called. Generating response."
                authenticationSuccessHandler.onAuthenticationSuccess(req, res, authResult)
            } else {
                log.debug "Continuing the filter chain"
                chain.doFilter(request, response)
            }
        } else {
            log.debug "Request does not contain any token. Letting it continue through the filter chain"
            chain.doFilter(request, response)
        }

    }

    AccessToken findToken(HttpServletRequest request) {
        def tokenValue = request.getParameter("token")
        return tokenValue ? new AccessToken(tokenValue) : null
    }
}